/**
 * Copyright @2019 Josin All Rights Reserved.
 * Author: Josin
 * Email : xeapplee@gmail.com
 */

/** Simple example for parsing http stream
 *
    #include <fc_string.h>
    #include <fc_http_req.h>
    
    char *str = "GET / HTTP/1.1\r\n"
                "Content-Length:11\r\n"
                "Connection:keep-alive\r\n"
                "\r\n"
                "Hello World"
                "GET /index.php HTTP/1.0\r\n"
                "Content-Length:5\r\n"
                "\r\n"
                "hellod";
    
    FC_PARSE_HTTP_STREAM(str, 122) {
        // Here has a `req` var point to FC_HTTP_REQ
        FCL_NODE        *node;
        FC_HTTP_HEADER  *header;
        printf("\n");
        FCL_LIST_FOREACH_HEAD(req->headers, node) {
            header = FCL_NODE_DATA_P(node);
            printf("[%s:%s]\n", header->key, header->val);
        } FCL_LIST_FOREACH_END();
        
        printf("{{%s:%s:%d:%ld}}\n", __FILE__, __FUNCTION__, __LINE__, __n);
    
    } FC_PARSE_HTTP_STREAM_END();
*/

#include <fc_string.h>
#include <fc_http_req.h>

static
FC_HTTP_REQ *NEW_FC_http_req()
{
    FC_HTTP_REQ *ptr = malloc( sizeof(FC_HTTP_REQ) );
    if ( !ptr ) {
        errno = -1;
        return NULL;
    }
    e_memzero(ptr, sizeof(FC_HTTP_REQ));
    return ptr;
}

static
FC_HTTP_HEADER *NEW_FC_http_header()
{
    FC_HTTP_HEADER *ptr = malloc( sizeof(FC_HTTP_HEADER) );
    if ( !ptr ) {
        errno = -1;
        return NULL;
    }
    e_memzero(ptr, sizeof(FC_HTTP_HEADER));
    return ptr;
}

int
TRASH_FC_http_header(void *data)
{
    FC_HTTP_HEADER *header = data;
    if (!header) {
        return FALSE;
    }
    e_memfree(header->key);
    e_memfree(header->val);
    e_memfree(header);
    return TRUE;
}

int
TRASH_FC_http_req(void *data)
{
    FC_HTTP_REQ *req = data;
    if (!req) {
        return FALSE;
    }
    e_memfree(req->body);
    e_memfree(req->http_version);
    e_memfree(req->request_url);
    e_memfree(req->range);
    if ( req->code )
    {
        e_memfree(req->code);
    }
    if ( req->msg )
    {
        e_memfree(req->msg);
    }
    fcl_list_destroy(req->headers);
    e_memfree(req);
    return TRUE;
}

static
void FC_set_http_req(FC_HTTP_REQ *req)
{
    if ( req->http_version && e_str8cmp(req->http_version, E_HTTP_1_0) )
    {
        req->http_ver_num = HTTP_1_0;
        return ;
    }
    else if ( req->http_version && e_str8cmp(req->http_version, E_HTTP_1_1) )
    {
        req->http_ver_num = HTTP_1_1;
        req->keep_alive   = KEEP_ALIVE;
        return ;
    }
    else if ( req->http_version && e_str8cmp(req->http_version, E_HTTP_2_0) )
    {
        req->http_ver_num = HTTP_2_0;
        req->keep_alive   = KEEP_ALIVE;
        return ;
    }
    
    if ( e_str3cmp(req->method, "GET") )
    {
        req->method_ver_num = HTTP_GET;
        return ;
    }
    else if ( e_str3cmp(req->method, "PUT"))
    {
        req->method_ver_num = HTTP_PUT;
        return ;
    }
    else if ( e_str4cmp(req->method, "POST"))
    {
        req->method_ver_num = HTTP_POST;
        return ;
    }
    else if ( e_str4cmp(req->method, "HEAD"))
    {
        req->method_ver_num = HTTP_HEAD;
        return ;
    }
    else if ( e_str6cmp(req->method, "DELETE"))
    {
        req->method_ver_num = HTTP_DELETE;
        return ;
    }
    else if ( e_str7cmp(req->method, "OPTIONS"))
    {
        req->method_ver_num = HTTP_OPTIONS;
        return ;
    }
}

static
void FC_set_http_header(FC_HTTP_REQ *req, FC_HTTP_HEADER *header)
{
    if ( e_str14ncmp(header->key, E_LENGTH) )
    {
        req->body_len = strtoull(header->val, NULL, 10);
    }
    else if (e_str5ncmp(header->key, E_RANGE))
    {
        req->range = header->val;
    }
    else if ( e_str10ncmp(header->key, E_CONNECT) )
    {
        if ( e_str10ncmp(header->val, E_KEEP) )
        {
            req->keep_alive = KEEP_ALIVE;
        }
        else
        {
            req->keep_alive = NOT_ALIVE;
        }
    }
}

FC_HTTP_REQ *
FC_http_stream_parse(char *src, unsigned long src_len, int direction, unsigned long *next_pos)
{
    if ( !src )   return NULL;
    
    FCL_LIST          *headers;
    FC_HTTP_REQ       *res;
    unsigned long      len,
                       prev_pos,
                       colon_pos;
    unsigned char      num;
    FC_HTTP_HEADER    *header;
    
    num        = 0;
    headers    = new_fcl_list();
    res        = NEW_FC_http_req();
    
    res->headers = headers;
    
/**
 * @brief NOTICE
 * Parse the HTTP request Header LINE
 * such as the following line:
 *
 * GET /index.html HTTP/1.1\r\n
 */
    for ( prev_pos = len = 0; src[len] != '\0'/* Not binary safe */; ++len )
    {
        if ( src[len] == ' ' )
        {
            if ( num == REQUEST_METHOD )
            {
                if ( direction == 1 )
                {
                    e_copymem(res->method, src+prev_pos, len - prev_pos);
                    /* Which method */
                    FC_set_http_req(res);
                }
                else
                {
                    res->http_version = e_substr(src + prev_pos, len - prev_pos);
                }
            }
            else if ( num == REQUEST_URL )
            {
                if ( direction == 1 )
                {
                    res->request_url = e_substr(src + prev_pos, len - prev_pos);
                }
                else
                {
                    res->code = e_substr(src + prev_pos, len - prev_pos);
                }
            }
            
            num++;
            
            prev_pos = len + 1; /* Current is space so skip it. */
        }
        if ( src[len] == '\r' && src[len + 1] == '\n' )
        {
            if ( num == REQUEST_HTTP_VERSION )
            {
                if ( direction == 1 )
                {
                    res->http_version = e_substr(src + prev_pos, len - prev_pos);
                    res->keep_alive   = NOT_ALIVE;
    
                    /* Get http version name */
                    FC_set_http_req(res);
    
                    prev_pos = len + 2;
                }
                else
                {
                    res->msg = e_substr(src + prev_pos, len - prev_pos);
                    prev_pos = len + 2;
                }
                break;
            }
        }
    }
    
/**
 * @brief NOTICE
 * Parse the HTTP request headers and body, for example:
 *
 * Content-Type: application/json;charset=UTF-8\r\n
 * Content-Length: 11\r\n
 * User-Agent: Exserver/1.1\r\n
 * \r\n
 * Hello World
 */
    colon_pos = 0;
    for ( len = prev_pos; src[len] != '\0'/* Not binary safe */; ++len )
    {
        if ( src[len] == ':' && !colon_pos )
        {
            colon_pos = len;
        }
        
        if ( (src[len] == '\r' && src[len + 1] == '\n'
              && src[len + 2] != '\r' && src[len + 3] != '\n')
             && colon_pos )
        {
            header = NEW_FC_http_header();
            header->key     = e_substr(src + prev_pos, colon_pos - prev_pos);
            header->key_len = colon_pos - prev_pos;
            colon_pos++;
            E_SKIP_SPACE(src, colon_pos);
            header->val     = e_substr(src + colon_pos, len - colon_pos);
            header->val_len = len - colon_pos;
            
            /* Get the Connection header */
            FC_set_http_header(res, header);
            
            /* Push into list */
            fcl_list_push(headers, header, TRASH_FC_http_header);
            prev_pos = len + 2;
            colon_pos = 0;
        }
        
        if ( (src[len] == '\r' && src[len + 1] == '\n' && src[len + 2] == '\r' && src[len + 3] == '\n') )
        {
/**
 * @brief NOTICE
 * Before HTTP Request Body, There is the last Request Header need to be add to the QUEUE_LIST
 */
            header = NEW_FC_http_header();
            header->key     = e_substr(src + prev_pos, colon_pos - prev_pos);
            header->key_len = colon_pos - prev_pos;
            colon_pos++;
            E_SKIP_SPACE(src, colon_pos);
            header->val     = e_substr(src + colon_pos, len - colon_pos);
            header->val_len = len - colon_pos;
            
            fcl_list_push(headers, header, TRASH_FC_http_header);
            
            /* Get the Connection header */
            FC_set_http_header(res, header);
            
/**
 * @brief NOTICE
 * Check whether the given data is valid or not.
 * if invalid, put into the trash can other than return
 */
            len += 4;
            /* body data */
            if ( res->method_ver_num < 0 ||
                 (res->body_len > (src_len - len))
                )
            {
                TRASH_FC_http_req(res);
                return NULL;
            }
            if ( res->body_len)
                res->body = e_substr(src + len, res->body_len);
/**
 * @brief NOTICE
 * The next start parsing pos was current position plus the len and body length
 */
            *next_pos += len + res->body_len;
            
            break;
        }
    }
    return res;
}

/**
 * @brief NOTICE
 * Some Macros for HTTP header parsing type geting macro
 */
FC_HTTP_HEADER_FUNC(header)
    FC_HTTP_HEADER_CMP(Content-Type,    contentType,    12,   if)
    FC_HTTP_HEADER_CMP(Content-Length,  contentLength,  14, elif)
    FC_HTTP_HEADER_CMP(Accept-Type,     acceptType,     11, elif)
FC_HTTP_HEADER_FUNC_END()